---
title: "Generative UI"
description: "Learn how to embed custom UI components in the chat window."
icon: "lucide/LayoutDashboard"
---

import UseClientCalloutSnippet from "@/snippets/use-client-callout.mdx";

# Render custom components in the chat UI

When a user interacts with your Copilot, you may want to render a custom UI component. The new hooks [`useRenderToolCall`](/reference/hooks/useRenderToolCall), [`useFrontendTool`](/reference/hooks/useFrontendTool), and [`useHumanInTheLoop`](/reference/hooks/useHumanInTheLoop) allow you to give the LLM the
option to render your custom component through the `render` property.

<Tabs groupId="gen-ui-type" items={['Render a component', 'Fetch data & render', 'renderAndWaitForResponse (HITL)', 'Render strings', 'Catch all renders']}>

  <Tab value="Render a component">
    [`useRenderToolCall`](/reference/hooks/useRenderToolCall) can be used with a `render` function to display information or UI elements within the chat.
    
    Here's an example to render a calendar meeting.

    <Frame>
      <img
        src="https://cdn.copilotkit.ai/docs/copilotkit/images/render-only-example.png"
        alt="Example of render-only Copilot tool"
        className="w-full h-auto max-w-md"
      />
    </Frame>

    ```tsx
    "use client" // only necessary if you are using Next.js with the App Router. // [!code highlight]
    import { useRenderToolCall } from "@copilotkit/react-core"; // [!code highlight]

    export function YourComponent() {
      useRenderToolCall({ // [!code highlight]
        name: "showCalendarMeeting",
        description: "Displays calendar meeting information",
        parameters: [
          {
            name: "date",
            type: "string",
            description: "Meeting date (YYYY-MM-DD)",
            required: true
          },
          {
            name: "time",
            type: "string",
            description: "Meeting time (HH:mm)",
            required: true
          },
          {
            name: "meetingName",
            type: "string",
            description: "Name of the meeting",
            required: false
          }
        ],
        // [!code highlight:14]
        render: ({ status, args }) => {
          const { date, time, meetingName } = args;

          if (status === 'inProgress') {
            return <LoadingView />; // Your own component for loading state
          } else {
            const meetingProps: CalendarMeetingCardProps = {
              date: date,
              time,
              meetingName
            };
            return <CalendarMeetingCardComponent {...meetingProps} />;
          }
        },
      });

      return (
        <>...</>
      );
    }
    ```

  </Tab>

  <Tab value="Fetch data & render">
    The [`useFrontendTool`](/reference/hooks/useFrontendTool) hook accepts both `handler` and `render` methods. The `handler` executes the tool, while `render` displays UI in the copilot chat window.

    <Frame>
      <img
        src="https://cdn.copilotkit.ai/docs/copilotkit/images/fetch-and-render.gif"
        alt="Example of fetch data + render in copilot chat"
        className="w-full h-auto max-w-md"
      />
    </Frame>

    ```tsx
    "use client" // only necessary if you are using Next.js with the App Router. // [!code highlight]
    import { useFrontendTool } from "@copilotkit/react-core"; // [!code highlight]

    useFrontendTool({ // [!code highlight]
      name: "showLastMeetingOfDay",
      description: "Displays the last calendar meeting for a given day",
      parameters: [
        {
          name: "date",
          type: "string",
          description: "Date to fetch the last meeting for (YYYY-MM-DD)",
          required: true
        }
      ],
      // [!code highlight:16]
      handler: async ({ date }) => {
        // some async operation which can return a result:
        const lastMeeting = await fetchLastMeeting(new Date(date));
        return lastMeeting;
      },
      render: ({ status, result }) => {
        if (status === 'executing' || status === 'inProgress') {
          // show a loading view while the tool is executing, i.e. while the meeting is being fetched
          return <LoadingView />;
        } else if (status === 'complete') {
          // show the meeting card once the tool is complete
          return <CalendarMeetingCardComponent {...result} />;
        } else {
          return <div className="text-red-500">No meeting found</div>;
        }
      },
    });
    ```

  </Tab>

  <Tab value="renderAndWaitForResponse (HITL)">
    The `render` method with [`useHumanInTheLoop`](/reference/hooks/useHumanInTheLoop) allows for returning values asynchronously from the render function.

    This is great for Human-in-the-Loop flows, where the AI assistant can prompt the end-user with a choice (rendered inside the chat UI),
    and the user can make the choice by pressing a button in the chat UI.

    ```tsx
    "use client" // only necessary if you are using Next.js with the App Router. // [!code highlight]
    import { useHumanInTheLoop } from "@copilotkit/react-core"; // [!code highlight]

    useHumanInTheLoop({ // [!code highlight]
      name: "handleMeeting",
      description: "Handle a meeting by booking or canceling",
      parameters: [
        {
          name: "meeting",
          type: "string",
          description: "The meeting to handle",
          required: true,
        },
        {
          name: "date",
          type: "string",
          description: "The date of the meeting",
          required: true,
        },
        {
          name: "title",
          type: "string",
          description: "The title of the meeting",
          required: true,
        },
      ],
      // [!code highlight:12]
      render: ({ args, respond, status }) => {
        const { meeting, date, title } = args;
        return (
          <MeetingConfirmationDialog
            meeting={meeting}
            date={date}
            title={title}
            onConfirm={() => respond?.('meeting confirmed')}
            onCancel={() => respond?.('meeting canceled')}
          />
        );
      },
    });
    ```

  </Tab>

  <Tab value="Render strings">
    For simple messages, you can return a string from the `render` method. This is useful for quick status updates or simple notifications.

    <Frame className="my-0">
      <img src="https://cdn.copilotkit.ai/docs/copilotkit/images/concepts/generative-ui/render-string.gif" className="w-[300px] p-0" alt="String rendering example" />
    </Frame>

    ```tsx
    "use client" // only necessary if you are using Next.js with the App Router. // [!code highlight]
    import { useFrontendTool } from "@copilotkit/react-core"; // [!code highlight]

    useFrontendTool({ // [!code highlight]
      name: "simpleTool",
      description: "A simple tool with string rendering",
      parameters: [
        {
          name: "taskName",
          type: "string",
          description: "Name of the task",
          required: true,
        },
      ],
      handler: async ({ taskName }) => {
        return await longRunningOperation(taskName);
      },
      // [!code highlight:3]
      render: ({ status, result }) => {
        return status === "complete" ? result : "Processing...";
      },
    });
    ```

  </Tab>

  <Tab value="Catch all renders">
    If you want to render tools that are not explicitly referenced in any specific hook call, you can pass `"*"` as the tool name with [`useDefaultTool`](/reference/hooks/useDefaultTool).
    In this case, the render method will receive an additional `name` parameter. This is particularly useful when working with agents that may call arbitrary tools - see more in our [Tool-based Generative UI](/crewai-crews/generative-ui/tool-based) guide.

    ```tsx
    import { useDefaultTool, CatchAllToolRenderProps } from "@copilotkit/react-core";

    useDefaultTool({
      render: ({ name, args, status, result }: CatchAllActionRenderProps<[]>) => {
        return <div>Rendering tool: {name}</div>;
      },
    });
    ```
    </Tab>

</Tabs>
<Accordions className="mt-4">
  <Accordion title="What do the different status states mean?">
    - `inProgress`: Arguments are dynamically streamed to the function, allowing you to adjust your UI in real-time.
    - `executing`: The tool handler is executing.
    - `complete`: The tool handler has completed execution.
  </Accordion>
  <Accordion title='Why do I need "use client" in Next.js with the App Router?'>
    <UseClientCalloutSnippet components={props.components} />
  </Accordion>
</Accordions>

## Test it out!

After defining the tool with a render method, ask the copilot to perform the task. For example, you can now ask the copilot to "show tasks" and see the custom UI component rendered in the chat interface.

<Callout type="info">
  You can read more about the tool hooks:
  - [`useRenderToolCall`](/reference/hooks/useRenderToolCall) for render-only tools
  - [`useFrontendTool`](/reference/hooks/useFrontendTool) for tools with handler + render
  - [`useHumanInTheLoop`](/reference/hooks/useHumanInTheLoop) for human-in-the-loop tools
</Callout>
